/*
 * Copyright (C) 2011-2012 Adam Gregoire <bsdunx@gmail.com>
 * Copyright (C) 2011-2012 Jeff Hubbard <lord2800@gmail.com>
 *
 * This file is part of the ScriptKit scripting engine library.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#ifndef __SMKIT_HPP__
#define __SMKIT_HPP__ 1

#include "config.hpp"

#ifdef HAVE_STL_THREAD
#include <thread>
#else
#include <boost/thread.hpp>
#endif

#include <vector>
#include <unordered_map>
#include <string>
#include <cstdint>

#include "jsapi.h"
#include "jsapi_ext.hpp"
#include "jsapi_ext_util.hpp"

#include "ScriptKit.hpp"

#include "Exports.hpp"

namespace ScriptKit
{
	extern JSAPI_EMPTY_CTOR(Global);

	extern JSClass global;

	extern JSFunctionSpec module_methods[];

	extern JSAPI_FUNC(mod_load);
	extern JSAPI_FUNC(mod_unload);
	extern JSAPI_FUNC(mod_require);
	extern JSAPI_FUNC(mod_addpath);

	// Forward declarations
	enum State;

	class SMKit : public IScriptKit
	{
	public:
		struct Script
		{
			struct Module
			{
				typedef JSModuleSpec* moduledef_t;
				std::string name;
				JSContext* context;		// This should really be a reference
				JSObject* global;
				JSObject* exports;
				JSObject* module;

				Module()
				{

				}
				~Module()
				{
					// A module should only ever be run in the context of its 
					// parent script, so this is deemed fine to blindly use.
					JS_RemoveObjectRoot(context, &global);
				}
			};
#ifdef HAVE_STL_THREAD
			std::thread thr;
#else
			boost::thread thr;
#endif
			JSRuntime* rt;
			JSContext* cx;

			JSObject* global;
			
			JSScript* jsscript;

			State state;
			std::string fileName;
			std::string fileDate;
			size_t fileNameHash;
			size_t fileDateHash;

			uint32_t runtimeSize;
			uint32_t stackChunkSize;

			std::vector<Module*> modules;
			std::unordered_map<JSClass*, JSObject*> constructors;

			Script(JSRuntime* rt, JSContext* cx, JSObject* global, uint32_t runtimeSize, uint32_t stackChunkSize) : 
			rt(rt), cx(cx), global(global), jsscript(nullptr), runtimeSize(runtimeSize), stackChunkSize(stackChunkSize)
			{
			}

			~Script()
			{
				if(thr.joinable())
					thr.join();
				{
					JS_SetRuntimeThread(rt);

					JSAutoRequest req(cx);
					JSAutoEnterCompartment comp;
					comp.enter(cx, global);

					for(auto it(modules.cbegin()), end(modules.cend()); it != end; ++it)
						delete *it;
				}

				JS_DestroyContext(cx);
				JS_DestroyRuntime(rt);
			}
		};

		SMKit(std::string& path);/* , JSClassInitCallback classCallback = nullptr,*/
			//JSErrorReporter reporter);
		virtual ~SMKit();

		std::vector<Script*> ScriptList(void) const;

		std::string& NormalizePath(std::string& path);
		bool FileExists(std::string& file);
		bool LoadFile(std::string& file, char** fbuf);

		IScript* GetScript(std::string& fileName);
		Script* CreateScriptObject(uint32_t runtimeSize, uint32_t stackChunkSize);
		IScript* CompileScript(std::string& fileName);

		const std::vector<std::string>& GetSearchPaths(void) const;

		void RegisterConstructor(Script* script, JSClass* classp, JSObject* ctor);
		JSObject* GetConstructor(Script* script, JSClass* classp);
		void DefineClasses(Script* script, JSObject* exports, JSClassSpec* classes);

		Script::Module* CompileModule(Script* script, std::string& file);
		JSModuleSpec* GetModuleDefinition(JSModuleSpec* modspec);
		bool RegisterModuleDefinition(IScript::Module::moduledef_t* definition);
		Script::Module* InitModule(Script* script, std::string& file);
		Script::Module* InitModule(Script* script, JSObject* obj, JSModuleSpec* modSpec);
		bool InitModules(Script* script);
		Script::Module* GetModule(Script* script, std::string& name);

		bool RunScript(IScript* script);

		bool Worker(Script* script);
		bool Shutdown();
		static JSBool ContextCallback(JSContext *cx, unsigned contextOp);
		static JSBool OperationCallback(JSContext *cx);
		static void ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report);

	private:
	
		std::string rootPath;	// Base path to restrict script loading from
		uint32_t runtimeMaxMem;		// Maximum amount of memory each runtime may use.
		JSErrorReporter reporter;
		std::vector<std::string> paths;
		std::vector<Script*> scripts;
		std::vector<JSModuleSpec*> mod_specs; // Registry of modules we recognize.
	};
}

#endif /* __SMKIT_HPP__ */
